// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_STRING_STREAM_H_
#define V8_STRING_STREAM_H_

#include "src/allocation.h"
#include "src/base/small-vector.h"
#include "src/handles.h"
#include "src/objects/heap-object.h"
#include "src/vector.h"

namespace v8 {
namespace internal {

    // Forward declarations.
    class ByteArray;

    class StringAllocator {
    public:
        virtual ~StringAllocator() = default;
        // Allocate a number of bytes.
        virtual char* allocate(unsigned bytes) = 0;
        // Allocate a larger number of bytes and copy the old buffer to the new one.
        // bytes is an input and output parameter passing the old size of the buffer
        // and returning the new size.  If allocation fails then we return the old
        // buffer and do not increase the size.
        virtual char* grow(unsigned* bytes) = 0;
    };

    // Normal allocator uses new[] and delete[].
    class HeapStringAllocator final : public StringAllocator {
    public:
        ~HeapStringAllocator() override { DeleteArray(space_); }
        char* allocate(unsigned bytes) override;
        char* grow(unsigned* bytes) override;

    private:
        char* space_;
    };

    class FixedStringAllocator final : public StringAllocator {
    public:
        FixedStringAllocator(char* buffer, unsigned length)
            : buffer_(buffer)
            , length_(length)
        {
        }
        ~FixedStringAllocator() override = default;

        char* allocate(unsigned bytes) override;
        char* grow(unsigned* bytes) override;

    private:
        char* buffer_;
        unsigned length_;
        DISALLOW_COPY_AND_ASSIGN(FixedStringAllocator);
    };

    template <std::size_t kInlineSize>
    class SmallStringOptimizedAllocator final : public StringAllocator {
    public:
        typedef base::SmallVector<char, kInlineSize> SmallVector;

        explicit SmallStringOptimizedAllocator(SmallVector* vector) V8_NOEXCEPT
            : vector_(vector) { }

        char* allocate(unsigned bytes) override
        {
            vector_->resize_no_init(bytes);
            return vector_->data();
        }

        char* grow(unsigned* bytes) override
        {
            unsigned new_bytes = *bytes * 2;
            // Check for overflow.
            if (new_bytes <= *bytes) {
                return vector_->data();
            }
            vector_->resize_no_init(new_bytes);
            *bytes = new_bytes;
            return vector_->data();
        }

    private:
        SmallVector* vector_;
    };

    class StringStream final {
        class FmtElm final {
        public:
            FmtElm(int value)
                : FmtElm(INT)
            { // NOLINT
                data_.u_int_ = value;
            }
            explicit FmtElm(double value)
                : FmtElm(DOUBLE)
            { // NOLINT
                data_.u_double_ = value;
            }
            FmtElm(const char* value)
                : FmtElm(C_STR)
            { // NOLINT
                data_.u_c_str_ = value;
            }
            FmtElm(const Vector<const uc16>& value)
                : FmtElm(LC_STR)
            { // NOLINT
                data_.u_lc_str_ = &value;
            }
            FmtElm(Object value)
                : FmtElm(OBJ)
            { // NOLINT
                data_.u_obj_ = value.ptr();
            }
            FmtElm(Handle<Object> value)
                : FmtElm(HANDLE)
            { // NOLINT
                data_.u_handle_ = value.location();
            }
            FmtElm(void* value)
                : FmtElm(POINTER)
            { // NOLINT
                data_.u_pointer_ = value;
            }

        private:
            friend class StringStream;
            enum Type { INT,
                DOUBLE,
                C_STR,
                LC_STR,
                OBJ,
                HANDLE,
                POINTER };

#ifdef DEBUG
            Type type_;
            explicit FmtElm(Type type)
                : type_(type)
            {
            }
#else
            explicit FmtElm(Type)
            {
            }
#endif

            union {
                int u_int_;
                double u_double_;
                const char* u_c_str_;
                const Vector<const uc16>* u_lc_str_;
                Address u_obj_;
                Address* u_handle_;
                void* u_pointer_;
            } data_;
        };

    public:
        enum ObjectPrintMode { kPrintObjectConcise,
            kPrintObjectVerbose };
        explicit StringStream(StringAllocator* allocator,
            ObjectPrintMode object_print_mode = kPrintObjectVerbose)
            : allocator_(allocator)
            , object_print_mode_(object_print_mode)
            , capacity_(kInitialCapacity)
            , length_(0)
            , buffer_(allocator_->allocate(kInitialCapacity))
        {
            buffer_[0] = 0;
        }

        bool Put(char c);
        bool Put(String str);
        bool Put(String str, int start, int end);
        void Add(const char* format) { Add(CStrVector(format)); }
        void Add(Vector<const char> format) { Add(format, Vector<FmtElm>()); }

        template <typename... Args>
        void Add(const char* format, Args... args)
        {
            Add(CStrVector(format), args...);
        }

        template <typename... Args>
        void Add(Vector<const char> format, Args... args)
        {
            FmtElm elems[] { args... };
            Add(format, ArrayVector(elems));
        }

        // Getting the message out.
        void OutputToFile(FILE* out);
        void OutputToStdOut() { OutputToFile(stdout); }
        void Log(Isolate* isolate);
        Handle<String> ToString(Isolate* isolate);
        std::unique_ptr<char[]> ToCString() const;
        int length() const { return length_; }

        // Object printing support.
        void PrintName(Object o);
        void PrintFixedArray(FixedArray array, unsigned int limit);
        void PrintByteArray(ByteArray ba);
        void PrintUsingMap(JSObject js_object);
        void PrintPrototype(JSFunction fun, Object receiver);
        void PrintSecurityTokenIfChanged(JSFunction function);
        // NOTE: Returns the code in the output parameter.
        void PrintFunction(JSFunction function, Object receiver, Code* code);

        // Reset the stream.
        void Reset()
        {
            length_ = 0;
            buffer_[0] = 0;
        }

        // Mentioned object cache support.
        void PrintMentionedObjectCache(Isolate* isolate);
        V8_EXPORT_PRIVATE static void ClearMentionedObjectCache(Isolate* isolate);
#ifdef DEBUG
        bool IsMentionedObjectCacheClear(Isolate* isolate);
#endif

        static const int kInitialCapacity = 16;

    private:
        void Add(Vector<const char> format, Vector<FmtElm> elms);
        void PrintObject(Object obj);

        StringAllocator* allocator_;
        ObjectPrintMode object_print_mode_;
        unsigned capacity_;
        unsigned length_; // does not include terminating 0-character
        char* buffer_;

        bool full() const { return (capacity_ - length_) == 1; }
        int space() const { return capacity_ - length_; }

        DISALLOW_IMPLICIT_CONSTRUCTORS(StringStream);
    };

} // namespace internal
} // namespace v8

#endif // V8_STRING_STREAM_H_
